/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package org.apache.asyncweb.common.codec;

import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.statemachine.ConsumeToCrLfDecodingState;
import org.apache.mina.filter.codec.statemachine.ConsumeToTerminatorDecodingState;
import org.apache.mina.filter.codec.statemachine.CrLfDecodingState;
import org.apache.mina.filter.codec.statemachine.DecodingState;
import org.apache.mina.filter.codec.statemachine.DecodingStateMachine;
import org.apache.mina.filter.codec.statemachine.LinearWhitespaceSkippingState;

/**
 * Decodes the Headers of HTTP requests. <code>HttpHeaderDecoder</code> employs
 * several sub-decoders - each taking the responsibility of decoding a specific
 * part of the header.<br/>
 * 
 * @author The Apache MINA Project (dev@mina.apache.org)
 */
abstract class HttpHeaderDecodingState extends DecodingStateMachine {

	private final CharsetDecoder asciiDecoder = HttpCodecUtils.US_ASCII_CHARSET
			.newDecoder();
	private final CharsetDecoder defaultDecoder = HttpCodecUtils.DEFAULT_CHARSET
			.newDecoder();

	private Map<String, List<String>> headers;
	private String lastHeaderName;
	private StringBuilder lastHeaderValue;

	HttpHeaderDecodingState() {
		// cookieParser = new CookieParser();
	}

	@Override
	protected DecodingState init() throws Exception {
		headers = new LinkedHashMap<String, List<String>>();
		return FIND_EMPTY_LINE;
	}

	@Override
	protected void destroy() throws Exception {
		headers = null;
		lastHeaderName = null;
		lastHeaderValue = null;
	}

	private final DecodingState FIND_EMPTY_LINE = new CrLfDecodingState() {
		@Override
		protected DecodingState finishDecode(boolean foundCRLF,
				ProtocolDecoderOutput out) throws Exception {
			if (foundCRLF) {
				out.write(headers);
				return null;
			} else {
				return READ_HEADER_NAME;
			}
		}
	};

	private final DecodingState READ_HEADER_NAME = new ConsumeToTerminatorDecodingState(
			(byte) ':') {
		@Override
		protected DecodingState finishDecode(IoBuffer product,
				ProtocolDecoderOutput out) throws Exception {
			lastHeaderName = product.getString(asciiDecoder);
			return AFTER_READ_HEADER_NAME;
		}
	};

	private final DecodingState AFTER_READ_HEADER_NAME = new LinearWhitespaceSkippingState() {
		@Override
		protected DecodingState finishDecode(int skippedBytes) throws Exception {
			lastHeaderValue = new StringBuilder();
			return READ_HEADER_VALUE;
		}
	};

	private final DecodingState READ_HEADER_VALUE = new ConsumeToCrLfDecodingState() {
		@Override
		protected DecodingState finishDecode(IoBuffer product,
				ProtocolDecoderOutput out) throws Exception {
			String value = product.getString(defaultDecoder);
			if (lastHeaderValue.length() == 0) {
				lastHeaderValue.append(value);
			} else {
				lastHeaderValue.append(' ');
				lastHeaderValue.append(value);
			}
			return AFTER_READ_HEADER_VALUE;
		}
	};

	private final DecodingState AFTER_READ_HEADER_VALUE = new LinearWhitespaceSkippingState() {
		@Override
		protected DecodingState finishDecode(int skippedBytes) throws Exception {
			if (skippedBytes == 0) {
				List<String> values = headers.get(lastHeaderName);
				if (values == null) {
					values = new ArrayList<String>();
					headers.put(lastHeaderName, values);
				}
				values.add(lastHeaderValue.toString());
				return FIND_EMPTY_LINE;
			} else {
				return READ_HEADER_VALUE;
			}
		}
	};
}
